﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using ZOIL.Framework.Core;
using ZOIL.Framework.Presentation;
using ZOIL.Library.Behaviors;

namespace ZOIL.Library.Devices
{
    public class MouseInputBehavior : ZInputBehavior<ZInformationLandscape>
    {
        #region private fields

        private IEnumerable<ZInformationLandscape> _parentLandscapes;

        private bool _isPanning;

        private bool _mouseDown;

        private Point _lastMousePosition;

        private Point _lastMouseDownPosition;

        private bool _animateWheelZoom;

        private double _wheelZoomFactor;

        #endregion

        #region public properties

        #region AnimateWheelZoom

        /// <summary>
        /// specifies if zooming with the mouse wheel is animated or not
        /// </summary>
        public bool AnimateWheelZoom
        {
            get
            {
                return _animateWheelZoom;
            }
            set
            {
                _animateWheelZoom = value;
                RaisePropertyChanged("AnimateWheelZoom");
            }
        }

        #endregion

        #region WheelZoomFactor

        /// <summary>
        /// specifies the factor by which the landscape is zoomed using the mouse wheel
        /// </summary>
        public double WheelZoomFactor
        {
            get
            {
                return _wheelZoomFactor;
            }
            set
            {
                _wheelZoomFactor = value;
                RaisePropertyChanged("WheelZoomFactor");
            }
        }

        #endregion

        #endregion

        #region ctor

        /// <summary>
        /// 
        /// </summary>
        public MouseInputBehavior()
        {
            AnimateWheelZoom = false;
            WheelZoomFactor = 0.95;
        }

        #endregion

        #region overrides

        protected override void OnAttached()
        {
            base.OnAttached();

            if (IsPanningEnabled || IsClickZoomEnabled)
            {
                AssociatedObject.MouseUp += OnMouseButton;
                AssociatedObject.MouseDown += OnMouseButton;

                if (IsPanningEnabled)
                    AssociatedObject.MouseMove += OnMouseMove;
            }

            if (IsZoomingEnabled)
                AssociatedObject.MouseWheel += OnMouseWheel;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.MouseMove -= OnMouseMove;
            AssociatedObject.MouseUp -= OnMouseButton;
            AssociatedObject.MouseDown -= OnMouseButton;
            AssociatedObject.MouseWheel -= OnMouseWheel;

            base.OnDetaching();
        }

        protected override void OnIsEnabledChanged(bool oldIsEnabled, bool newIsEnabled)
        {
            base.OnIsEnabledChanged(oldIsEnabled, newIsEnabled);

            if (newIsEnabled)
            {
                IsPanningEnabled = true;
                IsZoomingEnabled = true;
                IsClickZoomEnabled = true;
            }
            else
            {
                IsPanningEnabled = false;
                IsZoomingEnabled = false;
                IsClickZoomEnabled = false;
            }
        }

        protected override void OnIsPanningEnabledChanged(bool oldIsPanningEnabled, bool newIsPanningEnabled)
        {
            base.OnIsPanningEnabledChanged(oldIsPanningEnabled, newIsPanningEnabled);

            if (AssociatedObject == null)
                return;

            if (newIsPanningEnabled)
            {
                AssociatedObject.MouseMove += OnMouseMove;

                if (!IsClickZoomEnabled)
                {
                    AssociatedObject.MouseDown += OnMouseButton;
                    AssociatedObject.MouseUp += OnMouseButton;
                }
            }
            else
            {
                AssociatedObject.MouseMove -= OnMouseMove;

                if (!IsClickZoomEnabled)
                {
                    AssociatedObject.MouseDown -= OnMouseButton;
                    AssociatedObject.MouseUp -= OnMouseButton;
                }
            }
        }

        protected override void OnIsZoomingEnabledChanged(bool oldIsZoomingEnabled, bool newIsZoomingEnabled)
        {
            base.OnIsZoomingEnabledChanged(oldIsZoomingEnabled, newIsZoomingEnabled);

            if (AssociatedObject == null)
                return;

            if (newIsZoomingEnabled)
            {
                AssociatedObject.MouseWheel += OnMouseWheel;
            }
            else
            {
                AssociatedObject.MouseWheel -= OnMouseWheel;
            }
        }

        protected override void OnIsClickZoomEnabledChanged(bool oldIsClickZoomEnabled, bool newIsClickZoomEnabled)
        {
            base.OnIsClickZoomEnabledChanged(oldIsClickZoomEnabled, newIsClickZoomEnabled);

            if (AssociatedObject == null)
                return;

            if (newIsClickZoomEnabled)
            {
                if (!IsPanningEnabled)
                {
                    AssociatedObject.MouseDown += OnMouseButton;
                    AssociatedObject.MouseUp += OnMouseButton;
                }
            }
            else
            {
                if (!IsPanningEnabled)
                {
                    AssociatedObject.MouseDown -= OnMouseButton;
                    AssociatedObject.MouseUp -= OnMouseButton;
                }
            }
        }

        #endregion

        #region event handling

        private void OnMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta < 0)
            {
                AssociatedObject.ZoomBy(Mouse.GetPosition(AssociatedObject), WheelZoomFactor, AnimateWheelZoom);
            }
            else
            {
                AssociatedObject.ZoomBy(Mouse.GetPosition(AssociatedObject), 1.0 / WheelZoomFactor, AnimateWheelZoom);
            }
        }

        private void OnMouseButton(object sender, MouseButtonEventArgs e)
        {
            // ignore input of stylus devices, since we have another handler for this
            if (e.IsTouchOrigin())
                return;

#if DEBUG
            ZDebug.QuickLog("HandleMouseButton received: " + e.RoutedEvent);
#endif

            if (e.ButtonState == MouseButtonState.Pressed)
            {
                _mouseDown = true;

                _parentLandscapes = ZTreeHelper.TryFindParents<ZInformationLandscape>(AssociatedObject);
                _lastMouseDownPosition = e.GetPosition(null);
                AssociatedObject.CaptureMouse();

                e.Handled = true;

            }
            else if (e.ButtonState == MouseButtonState.Released)
            {
                AssociatedObject.ReleaseMouseCapture();
                if (_isPanning)
                {
                    _isPanning = false;
                    e.Handled = true;
                    _mouseDown = false;
                    return;
                }

                var clickedElement = Mouse.DirectlyOver as FrameworkElement;
                if (clickedElement != null)
                {
#if DEBUG
                    var elementIsZoomTarget = ZInformationLandscape.GetZoomTarget(clickedElement);
                    ZDebug.QuickLog("HandleMouseButton MouseUp over " + clickedElement + " elementIsZoomTarget=" + elementIsZoomTarget);
#endif
                    RaiseClickZoomEvent(clickedElement);
                    _mouseDown = false;
                    e.Handled = true;
                }
            }
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (_isPanning && _mouseDown)
            {
                var position = Mouse.GetPosition(null);
                var scale = AssociatedObject.Animation.Position.Zoom;

                // look for all parent Informationlandscapes and adjust the scale factor
                foreach (var parentLandscape in _parentLandscapes)
                {
                    var scale2 = parentLandscape.Animation.Position.Zoom;
                    scale *= scale2;
                }

                var dx = (position.X - _lastMousePosition.X) / scale;
                var dy = (position.Y - _lastMousePosition.Y) / scale;
                var delta = new Vector(dx, dy);

                AssociatedObject.PanBy(delta, false);
            }
            else if (_mouseDown && (Math.Abs(_lastMouseDownPosition.X - e.GetPosition(null).X) > 10 || Math.Abs(_lastMouseDownPosition.Y - e.GetPosition(null).Y) > 10))
            {
                _isPanning = true;
            }

            _lastMousePosition = e.GetPosition(null);
        }

        #endregion
    }
}
